package ace.cmp.application.core.support.handler;

import ace.cmp.core.enums.SystemCodeEnum;
import ace.cmp.core.exception.BusinessException;
import ace.cmp.core.exception.SystemException;
import ace.cmp.core.model.R;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Path;
import java.util.Objects;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.method.HandlerMethod;

/**
 * 异常处理含: 1.controller层面异常.
 *
 * @author Caspar
 */
@Slf4j
public class WebExceptionHandler {

  /**
   * 异常统一处理.
   *
   * @param request request
   * @param response response
   * @param handlerMethod handlerMethod
   * @param ex 异常
   * @return 返回null值，系统继续搜索其它处理程序handler,返回R，则系统不继续搜索其它handler，直接返回给上层
   */
  public R<Object> execute(
      HttpServletRequest request,
      HttpServletResponse response,
      @Nullable HandlerMethod handlerMethod,
      Exception ex) {
    if (ex instanceof BusinessException) {
      return handleBusinessException((BusinessException) ex);
    } else if (ex instanceof SystemException) {
      return handleSystemException((SystemException) ex);
    } else if (ex instanceof AccessDeniedException) {
      return handleAccessDeniedException(request, response, ex);
    } else if (ex instanceof MethodArgumentNotValidException) {
      return handleValidateException((MethodArgumentNotValidException) ex);
    } else if (ex instanceof BindException) {
      return handleBindException((BindException) ex);
    } else if (ex instanceof MissingServletRequestParameterException) {
      return handleMissingServletRequestParameterException(
          (MissingServletRequestParameterException) ex);
    } else if (ex instanceof ConstraintViolationException) {
      return handleConstraintViolationException((ConstraintViolationException) ex);
    } else if (ex instanceof HttpRequestMethodNotSupportedException) {
      return handlerHttpRequestMethodNotSupportedException(
          (HttpRequestMethodNotSupportedException) ex);
    } else if (ex instanceof HttpMediaTypeNotSupportedException) {
      return handlerHttpMediaTypeNotSupportedException((HttpMediaTypeNotSupportedException) ex);
    } else if (response.getStatus() == 404) {
      return handler404HttpStatus(request, response, handlerMethod, ex);
    } else if (response.getStatus() == 403) {
      return handler403HttpStatus(request, response, handlerMethod, ex);
    } else if (ex.getClass().equals(RuntimeException.class)) {
      return handlerRuntimeException((RuntimeException) ex);
    } else {
      return handlerAllException(ex);
    }
  }

  private R<Object> handleAccessDeniedException(
      HttpServletRequest request, HttpServletResponse response, Exception ex) {
    response.setStatus(HttpStatus.FORBIDDEN.value());
    return R.builder()
        .code(SystemCodeEnum.ERROR_HTTP_403_EXCEPTION.getCode())
        .message(SystemCodeEnum.ERROR_HTTP_403_EXCEPTION.getDesc())
        .build();
  }

  private R<Object> handleMissingServletRequestParameterException(
      MissingServletRequestParameterException ex) {
    return R.builder()
        .code(SystemCodeEnum.ERROR_CHECK_PARAMETER.getCode())
        .message(ex.getMessage())
        .build();
  }

  private R<Object> handleConstraintViolationException(ConstraintViolationException ex) {
    Optional<ConstraintViolation<?>> constraintViolation =
        ex.getConstraintViolations().stream().findFirst();
    String path =
        constraintViolation
            .map(ConstraintViolation::getPropertyPath)
            .map(Path::toString)
            .orElse("");
    if (!path.isEmpty()) {
      path = path + ",";
    }
    String message = path + constraintViolation.map(ConstraintViolation::getMessage).orElse("");
    return R.builder()
        .code(SystemCodeEnum.ERROR_CHECK_PARAMETER.getCode())
        .message(message)
        .build();
  }

  /**
   * 获取错误接口uri.
   *
   * @param request request
   * @return 错误接口uri
   */
  private String getErrorRequestUri(HttpServletRequest request) {
    Object value = request.getAttribute("javax.servlet.error.request_uri");
    if (Objects.isNull(value)) {
      return "";
    }
    return (String) value;
  }

  private R<Object> handleBusinessException(BusinessException ex) {
    return R.builder().code(ex.getCode()).message(ex.getMessage()).data(ex.getExtras()).build();
  }

  private R<Object> handler404HttpStatus(
      HttpServletRequest request,
      HttpServletResponse response,
      HandlerMethod handlerMethod,
      Exception ex) {
    response.setStatus(HttpStatus.NOT_FOUND.value());
    R<Object> r = R.create(SystemCodeEnum.ERROR_HTTP_404_EXCEPTION);
    r.setData(getErrorRequestUri(request));
    return r;
  }

  private R<Object> handler403HttpStatus(
      HttpServletRequest request,
      HttpServletResponse response,
      HandlerMethod handlerMethod,
      Exception ex) {
    response.setStatus(HttpStatus.FORBIDDEN.value());
    R<Object> r = R.create(SystemCodeEnum.ERROR_HTTP_403_EXCEPTION);
    r.setData(getErrorRequestUri(request));
    return r;
  }

  private R<Object> handlerAllException(Exception ex) {
    log.error("handler exception", ex);
    return R.create(SystemCodeEnum.ERROR_SYSTEM_EXCEPTION);
  }

  private R<Object> handlerRuntimeException(RuntimeException ex) {
    log.error("handler runtime exception", ex);
    return R.builder()
        .code(SystemCodeEnum.ERROR_SYSTEM_EXCEPTION.getCode())
        .message(ex.getMessage())
        .build();
  }

  private R<Object> handlerHttpMediaTypeNotSupportedException(
      HttpMediaTypeNotSupportedException ex) {
    return R.builder()
        .code(SystemCodeEnum.ERROR_SYSTEM_EXCEPTION.getCode())
        .message(ex.getMessage())
        .build();
  }

  private R<Object> handlerHttpRequestMethodNotSupportedException(
      HttpRequestMethodNotSupportedException ex) {
    return R.builder()
        .code(SystemCodeEnum.ERROR_SYSTEM_EXCEPTION.getCode())
        .message(ex.getMessage())
        .build();
  }

  //    private R handlerClientAbortException(ClientAbortException ex) {
  //        // 客户关掉了浏览器引起异常，简单记录
  //        log.warn(" System Error ClientAbortException", ex);
  //        return R.create(SystemCodeEnum.ERROR_CLIENT_ABORT_EXCEPTION);
  //    }

  private R<Object> handleSystemException(SystemException ex) {
    log.error(String.format("code:%s,message:%s", ex.getCode(), ex.getMessage()), ex);
    return R.builder().code(ex.getCode()).message(ex.getMessage()).build();
  }

  /**
   * 参数验证、绑定异常.
   *
   * @param ex 绑定异常
   * @return 转换异常信息
   */
  private R<Object> handleBindException(BindException ex) {
    R<Object> response = new R<>();
    if (Objects.nonNull(ex.getBindingResult().getFieldError())
        && !ex.getBindingResult().getFieldError().isBindingFailure()) {
      String msg =
          String.format(
              "%s,%s",
              ex.getBindingResult().getFieldError().getField(),
              ex.getBindingResult().getFieldError().getDefaultMessage());
      response.setCode(SystemCodeEnum.ERROR_CHECK_PARAMETER.getCode());
      response.setMessage(msg);
    } else {
      response.setCode(SystemCodeEnum.ERROR_INVALID_PARAMETER.getCode());
      response.setMessage(SystemCodeEnum.ERROR_INVALID_PARAMETER.getDesc());
    }
    return response;
  }

  /**
   * 参数验证、绑定异常.
   *
   * @param ex 参数验证、绑定异常
   * @return 转换异常
   */
  private R<Object> handleValidateException(MethodArgumentNotValidException ex) {
    R<Object> response = new R<>();
    if (Objects.nonNull(ex.getBindingResult().getFieldError()) &&
        !ex.getBindingResult().getFieldError().isBindingFailure()
    ) {
      StringBuilder stringBuilder = new StringBuilder();
      if (!ex.getBindingResult().getFieldError().getObjectName().isEmpty()) {
        stringBuilder.append(ex.getBindingResult().getFieldError().getObjectName());
      }
      if (!ex.getBindingResult().getFieldError().getField().isEmpty()) {
        if (!stringBuilder.isEmpty()) {
          stringBuilder.append(".");
        }
        stringBuilder.append(ex.getBindingResult().getFieldError().getField());
      }

      if (!stringBuilder.isEmpty()) {
        stringBuilder.append(", ");
      }
      stringBuilder.append("value: ")
          .append(ex.getBindingResult().getFieldError().getRejectedValue());

      if (ex.getBindingResult().getFieldError().getDefaultMessage() != null
          && !ex.getBindingResult().getFieldError().getDefaultMessage().isEmpty()) {
        if (!stringBuilder.isEmpty()) {
          stringBuilder.append(", ");
        }
        stringBuilder.append("reason: ")
            .append(ex.getBindingResult().getFieldError().getDefaultMessage());
      }
      response.setCode(SystemCodeEnum.ERROR_CHECK_PARAMETER.getCode());
      response.setMessage(stringBuilder.toString());
    } else {
      response.setCode(SystemCodeEnum.ERROR_INVALID_PARAMETER.getCode());
      response.setMessage(SystemCodeEnum.ERROR_INVALID_PARAMETER.getDesc());
    }
    return response;
  }
}
